﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using FreeSql;
using Microsoft.AspNetCore.Identity;

namespace IGeekFan.AspNetCore.Identity.FreeSql;

/// <summary>
/// Base class for the Entity Framework database context used for identity.
/// </summary>
/// <typeparam name="TUser">The type of the user objects.</typeparam>
public class IdentityUserContext<TUser> : IdentityUserContext<TUser, string> where TUser : IdentityUser
{
    /// <summary>
    /// Initializes a new instance of <see cref="IdentityUserContext{TUser}"/>.
    /// </summary>
    /// <param name="identityOptions">The options to be used by a <see cref="IdentityOptions"/></param>
    /// <param name="fsql">The options to be used by a <see cref="IFreeSql"/></param>
    /// <param name="options">The options to be used by a <see cref="DbContextOptions"/></param>
    public IdentityUserContext(IdentityOptions identityOptions, IFreeSql fsql, DbContextOptions options) : base(identityOptions, fsql, options) { }

    /// <summary>
    /// Initializes a new instance of the <see cref="IdentityUserContext{TUser}" /> class.
    /// </summary>
    protected IdentityUserContext() { }
}

/// <summary>
/// Base class for the Entity Framework database context used for identity.
/// </summary>
/// <typeparam name="TUser">The type of user objects.</typeparam>
/// <typeparam name="TKey">The type of the primary key for users and roles.</typeparam>
public class IdentityUserContext<TUser, TKey> : IdentityUserContext<TUser, TKey, IdentityUserClaim<TKey>, IdentityUserLogin<TKey>, IdentityUserToken<TKey>>
    where TUser : IdentityUser<TKey>
    where TKey : IEquatable<TKey>
{
    /// <summary>
    /// Initializes a new instance of the db context.
    /// </summary>
    /// <param name="identityOptions"></param>
    /// <param name="fsql"></param>
    /// <param name="options">The options to be used by a <see cref="DbContext"/>.</param>
    public IdentityUserContext(IdentityOptions identityOptions, IFreeSql fsql, DbContextOptions options) : base(identityOptions, fsql, options) { }

    /// <summary>
    /// Initializes a new instance of the class.
    /// </summary>
    protected IdentityUserContext() { }
}

/// <summary>
/// Base class for the Entity Framework database context used for identity.
/// </summary>
/// <typeparam name="TUser">The type of user objects.</typeparam>
/// <typeparam name="TKey">The type of the primary key for users and roles.</typeparam>
/// <typeparam name="TUserClaim">The type of the user claim object.</typeparam>
/// <typeparam name="TUserLogin">The type of the user login object.</typeparam>
/// <typeparam name="TUserToken">The type of the user token object.</typeparam>
public abstract class IdentityUserContext<TUser, TKey, TUserClaim, TUserLogin, TUserToken> : DbContext
    where TUser : IdentityUser<TKey>
    where TKey : IEquatable<TKey>
    where TUserClaim : IdentityUserClaim<TKey>
    where TUserLogin : IdentityUserLogin<TKey>
    where TUserToken : IdentityUserToken<TKey>
{
    /// <summary>
    /// Initializes a new instance of the class.
    /// </summary>
    /// <param name="identityOptions"></param>
    /// <param name="fsql"></param>
    /// <param name="options">The options to be used by a <see cref="DbContext"/>.</param>
    public IdentityUserContext(IdentityOptions identityOptions, IFreeSql fsql, DbContextOptions options) : base(fsql, options)
    {
        IdentityOptions = identityOptions;
        OnModelCreating(Orm.CodeFirst);
    }

    /// <summary>
    /// Initializes a new instance of the class.
    /// </summary>
    protected IdentityUserContext() { }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of Users.
    /// </summary>
    public virtual DbSet<TUser> Users { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of User claims.
    /// </summary>
    public virtual DbSet<TUserClaim> UserClaims { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of User logins.
    /// </summary>
    public virtual DbSet<TUserLogin> UserLogins { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of User tokens.
    /// </summary>
    public virtual DbSet<TUserToken> UserTokens { get; set; }

    private IdentityOptions IdentityOptions
    {
        get;
    }

    //private class PersonalDataConverter : ValueConverter<string, string>
    //{
    //    public PersonalDataConverter(IPersonalDataProtector protector) : base(s => protector.Protect(s), s => protector.Unprotect(s), default)
    //    { }
    //}

    /// <summary>
    /// Configures the schema needed for the identity framework.
    /// </summary>
    /// <param name="builder">
    /// The builder being used to construct the model for this context.
    /// </param>
    protected override void OnModelCreating(ICodeFirst builder)
    {
        //if (IdentityOptions == null) return;

        var storeOptions = IdentityOptions?.Stores;
        var maxKeyLength = storeOptions?.MaxLengthForKeys ?? 0;
        var protectPersonalData = storeOptions?.ProtectPersonalData ?? false;
        //PersonalDataConverter converter = null;

        builder.Entity<TUser>(b =>
        {
            b.HasKey(u => u.Id);
            b.HasIndex(u => u.NormalizedUserName).HasName("UserNameIndex").IsUnique();
            b.HasIndex(u => u.NormalizedEmail).HasName("EmailIndex");
            b.ToTable("AspNetUsers");
            b.Property(u => u.ConcurrencyStamp).IsRowVersion();
            //b.Property(u => u.ConcurrencyStamp).IsRowVersion().Help().MapType(typeof(byte[]));

            b.Property(u => u.UserName).HasMaxLength(256);
            b.Property(u => u.NormalizedUserName).HasMaxLength(256);
            b.Property(u => u.Email).HasMaxLength(256);
            b.Property(u => u.NormalizedEmail).HasMaxLength(256);

            //if (encryptPersonalData)
            //{
            //    converter = new PersonalDataConverter(this.GetService<IPersonalDataProtector>());
            //    var personalDataProps = typeof(TUser).GetProperties().Where(
            //                    prop => Attribute.IsDefined(prop, typeof(ProtectedPersonalDataAttribute)));
            //    foreach (var p in personalDataProps)
            //    {
            //        if (p.PropertyType != typeof(string))
            //        {
            //            throw new InvalidOperationException(CoreStrings.CanOnlyProtectStrings);
            //        }
            //        b.Property(typeof(string), p.Name).HasConversion(converter);
            //    }
            //}

            //b.HasMany<TUserClaim>().WithOne().HasForeignKey(uc => uc.UserId).IsRequired();
            //b.HasMany<TUserLogin>().WithOne().HasForeignKey(ul => ul.UserId).IsRequired();
            //b.HasMany<TUserToken>().WithOne().HasForeignKey(ut => ut.UserId).IsRequired();
        });

        builder.Entity<TUserClaim>(b =>
        {
            b.HasKey(uc => uc.Id);
            b.Property(r => r.Id).Help().IsIdentity(true);
            b.ToTable("AspNetUserClaims");
        });

        builder.Entity<TUserLogin>(b =>
        {
            b.HasKey(l => new { l.LoginProvider, l.ProviderKey });

            if (maxKeyLength > 0)
            {
                b.Property(l => l.LoginProvider).HasMaxLength(maxKeyLength);
                b.Property(l => l.ProviderKey).HasMaxLength(maxKeyLength);
            }

            b.ToTable("AspNetUserLogins");
        });

        builder.Entity<TUserToken>(b =>
        {
            b.HasKey(t => new { t.UserId, t.LoginProvider, t.Name });

            if (maxKeyLength > 0)
            {
                b.Property(t => t.LoginProvider).HasMaxLength(maxKeyLength);
                b.Property(t => t.Name).HasMaxLength(maxKeyLength);
            }

            //if (encryptPersonalData)
            //{
            //    var tokenProps = typeof(TUserToken).GetProperties().Where(
            //                    prop => Attribute.IsDefined(prop, typeof(ProtectedPersonalDataAttribute)));
            //    foreach (var p in tokenProps)
            //    {
            //        if (p.PropertyType != typeof(string))
            //        {
            //            throw new InvalidOperationException(CoreStrings.CanOnlyProtectStrings);
            //        }
            //        b.Property(typeof(string), p.Name).HasConversion(converter);
            //    }
            //}

            b.ToTable("AspNetUserTokens");
        });
    }
}
